
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

package com.hlkj.pay.interceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import com.aliyun.tea.utils.StringUtils;
import com.hlkj.framework.caches.service.RedisTemplateService;
import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.common.pojo.LocalContext;
import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.framework.common.util.web.WebFrameworkUtils;
import com.hlkj.pay.common.CommonResultCode;
import com.hlkj.pay.common.constants.AdminConstant;
import com.hlkj.pay.common.constants.MqConstant;
import com.hlkj.pay.config.ExtRocketMQTemplate;
import com.hlkj.pay.dto.LocalAdminUserRequest;
import com.hlkj.pay.enums.AuthTokenEnum;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.enums.MerchantEnum;
import com.hlkj.pay.infrastructure.model.admin.AdminUserBehaviorLogDO;
import com.hlkj.pay.infrastructure.model.token.TokenSecretDO;
import com.hlkj.pay.service.merchant.IMerchantService;
import com.hlkj.pay.service.token.ITokenService;
import com.hlkj.pay.util.TokenUtils;
import com.hlkj.pay.vo.common.resp.token.TokenInfoResp;

import cn.hutool.crypto.digest.MD5;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @author HlpayTeam
 * @data 2022/2/8 13:22
 */
@Component
@Slf4j
@Order(Integer.MIN_VALUE)
public class AdminTokenInterceptor implements HandlerInterceptor {

    @Value("${auth.signingKey}")
    private String signingKey;

    @Value("${spring.profiles.active}")
    private String active;

    @Autowired
    private ExtRocketMQTemplate extRocketMQTemplate;

    @Autowired
    private RedisTemplateService redisTemplateService;

    @Autowired
    private IMerchantService merchantService;

    @Autowired
    private ITokenService tokenService;

    private final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 这里是个坑，因为带请求带headers时，ajax会发送两次请求，
        // 第一次会发送OPTIONS请求，第二次才会发生get/post请求，所以要放行OPTIONS请求
        // 如果是OPTIONS请求，让其响应一个 200状态码，说明可以正常访问
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            // 放行OPTIONS请求
            return true;
        }
        // token 获取
        String header = WebFrameworkUtils.getRequest().getHeader(AdminConstant.AUTHORIZATION);
        if (StringUtil.isEmpty(header)) {
            ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
            this.errorHttpResponseConverter.write(CommonResult.error(CommonResultCode.TOKEN_PARAMETER_MISSING), MediaType.APPLICATION_JSON, httpResponse);
            log.warn("token is null:{}", header);
            return false;
        }
        String token = header.replace(AdminConstant.BEARER_BANK, "");
        token = token.replace(AdminConstant.BEARER, "");
        if (StringUtil.isEmpty(token)) {
            ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
            this.errorHttpResponseConverter.write(CommonResult.error(CommonResultCode.TOKEN_PARAMETER_MISSING), MediaType.APPLICATION_JSON, httpResponse);
            log.warn("token is null:{}", token);
            return false;
        }

        // 本地不需要校验token
        if(!active.equals("local")){
            boolean checkToken = TokenUtils.checkToken(token, signingKey);
            if (!checkToken) {
                ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
                this.errorHttpResponseConverter.write(CommonResult.error(CommonResultCode.TOKEN_PARAMETER_ERROR), MediaType.APPLICATION_JSON, httpResponse);
                log.warn("checkToken fail token:{}", token);
                return false;
            }
        }

        TokenInfoResp accessTokenInfo = TokenUtils.parseToken(token);
        if (log.isDebugEnabled()) {
            log.debug("TokenInfo:{}", JsonUtils.toJsonString(accessTokenInfo));
        }
        // token 失效
        Long expireTime = accessTokenInfo.getExpireTime();
        long currentTimeMillis = System.currentTimeMillis();
        long now = currentTimeMillis / 1000;
        String tokenKey = "token:" + MD5.create().digestHex(token);
        if (!redisTemplateService.hasKey(tokenKey) && !"local".equals(active)&& !"dev".equals(active)) {
            if (expireTime == null || now > expireTime) {
                ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
                this.errorHttpResponseConverter.write(CommonResult.error(CommonResultCode.TOKEN_PARAMETER_EXPIRE), MediaType.APPLICATION_JSON, httpResponse);
                log.warn("token is expire now:{},expireTime:{}", now, expireTime);
                return false;
            }
        }
        TokenSecretDO tokenSecretDO = tokenService.queryTenantInfoByDomain();
        if (tokenSecretDO == null) {
            ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
            this.errorHttpResponseConverter.write(CommonResult.error(CommonResultCode.TOKEN_PARAMETER_ERROR), MediaType.APPLICATION_JSON, httpResponse);
            log.warn("token is not accessToken accessTokenInfo:{}", JsonUtils.toJsonString(accessTokenInfo));
            return false;
        }
        redisTemplateService.set(tokenKey, token, tokenSecretDO.getAccessTokenValidity());
        AuthTokenEnum.AUTH_TOKEN_TYPE authTokenType = AuthTokenEnum.AUTH_TOKEN_TYPE.from(accessTokenInfo.getAuthTokenType());
        switch (authTokenType) {
            case REFRESH_TOKEN:
                ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
                this.errorHttpResponseConverter.write(CommonResult.error(CommonResultCode.TOKEN_PARAMETER_ERROR), MediaType.APPLICATION_JSON, httpResponse);
                log.warn("token is not accessToken accessTokenInfo:{}", JsonUtils.toJsonString(accessTokenInfo));
                return false;
        }
        LocalAdminUserRequest adminUserRequest = new LocalAdminUserRequest();
        log.debug("info:{}", JsonUtils.toJsonString(accessTokenInfo));
        // LocalAdminUserRequest adminUserRequest = new LocalAdminUserRequest();
        adminUserRequest.setUserId(accessTokenInfo.getUserId());
        adminUserRequest.setRequestMethod(request.getMethod());
        adminUserRequest.setAccessToken(token);
        adminUserRequest.setLogCode(UUID.randomUUID().toString());
        adminUserRequest.setSysType(accessTokenInfo.getSysType());
        String sn = accessTokenInfo.getSn();
        if (org.springframework.util.StringUtils.hasText(sn)) {
            if (!sn.startsWith(MerchantEnum.MERCHANT_SN_TYPE.AGENT.getSnPrefix()) && !sn.startsWith(MerchantEnum.MERCHANT_SN_TYPE.MERCHANT.getSnPrefix())) {
                sn = null;
            }
        }
        else {
            sn = null;
        }
        adminUserRequest.setSn(sn);
        adminUserRequest.setStartTime(currentTimeMillis);
        String servletPath = request.getServletPath();
        if (StringUtils.isEmpty(servletPath)) {
            servletPath = request.getPathInfo();
        }
        adminUserRequest.setRequestUrl(servletPath);
        switch (CommonEnum.SYS_TYPE.from(accessTokenInfo.getSysType())) {
            case BUSINESS:
                break;
            case AGENT:
                List<String> querySnList = merchantService.queryMerchantByAgentId(sn);
                if (CollectionUtils.isEmpty(querySnList)) {
                    querySnList = new ArrayList<>();
                }
                querySnList.add(sn);
                adminUserRequest.setQuerySnList(querySnList);
                adminUserRequest.setQuerySysType(accessTokenInfo.getSysType());
                break;
            default:
                adminUserRequest.setQuerySn(sn);
                adminUserRequest.setQuerySysType(accessTokenInfo.getSysType());
        }
        adminUserRequest.setSysType(accessTokenInfo.getSysType());
        LocalContext.set(adminUserRequest);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        LocalAdminUserRequest localAdminUserRequest = (LocalAdminUserRequest) LocalContext.get();
        AdminUserBehaviorLogDO adminUserBehaviorLogDO = new AdminUserBehaviorLogDO();
        adminUserBehaviorLogDO.setUserId(localAdminUserRequest.getUserId());
        adminUserBehaviorLogDO.setLogCode(localAdminUserRequest.getLogCode());
        adminUserBehaviorLogDO.setPermissionCode(localAdminUserRequest.getPermissionCode());
        adminUserBehaviorLogDO.setParams(localAdminUserRequest.getParams());
        adminUserBehaviorLogDO.setResponse(localAdminUserRequest.getResponse());
        adminUserBehaviorLogDO.setIp(localAdminUserRequest.getIp());
        adminUserBehaviorLogDO.setIpLocation(localAdminUserRequest.getIpLocation());
        adminUserBehaviorLogDO.setRequestUrl(localAdminUserRequest.getRequestMethod() + " " + localAdminUserRequest.getRequestUrl());
        adminUserBehaviorLogDO.setCostTime(System.currentTimeMillis() - localAdminUserRequest.getStartTime());
        adminUserBehaviorLogDO.setCreateTime(System.currentTimeMillis());
        try {
            extRocketMQTemplate.send(MqConstant.BEHAVIOR_LOG_PARAMS_TOPIC, adminUserBehaviorLogDO);
        }
        catch (Exception e) {
            log.error("请求参数记录失败:{}", e.getMessage());
        }
        LocalContext.destroy();
    }

    public static void main(String[] args) {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE3MzEwNDU4OTEsInN5c1R5cGUiOiIxIiwiYXV0aFRva2VuVHlwZSI6IjEiLCJhcHBJZCI6ImJ1c2luZXNzX2FwcF9pZCIsInNuIjoiMCIsImV4cCI6MTczMTA0NzY5MSwiaWF0IjoxNzMxMDQ1ODkxLCJ1c2VySWQiOiIxIn0.t1h4b5-6gkdC8rx-Jq8uSd_GispP3D5e2fjxhFi7ozU";

        String tokenKey = "token:" + MD5.create().digestHex(token);
        System.out.println(tokenKey);

    }
}
